package cn.spring.framework.redis.core.redis;

import cn.hutool.core.thread.ThreadUtil;
import cn.spring.framework.redis.codec.Codec;
import cn.spring.framework.redis.codec.CodecEnum;
import cn.spring.framework.redis.codec.CodecFactory;
import cn.spring.framework.redis.codec.data.MapData;
import cn.spring.framework.redis.config.LzmhRedisProperties;
import cn.spring.framework.redis.constant.Constants;
import cn.spring.framework.redis.core.FillCache;
import cn.spring.framework.redis.mock.Mock;
import cn.spring.framework.redis.mock.MockRegister;
import cn.spring.framework.redis.service.RedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author xingkong
 * @Title: AbstractRedisOperator
 * @Description: redis操作的父类
 * @date 2021/11/12 16:09
 */
public abstract class AbstractRedisOperator {

    private static final Logger log = LoggerFactory.getLogger(AbstractRedisOperator.class);

    protected final LzmhRedisProperties lzmhRedisProperties;
    /**
     * 服务器交互接口 RedisTransporter
     */
    protected RedisService redisService;

    /**
     * 本地锁，一个 key 对应一个 Lock
     */
    private static final Map<String, Lock> LOCAL_LOCK = new ConcurrentHashMap<>();
    /**
     * REFRESH_LOCK 的 key 为当前操作的 redis 的 key， AtomicInteger 为 0 ，则说明当前 key 没有在刷新；为 1 则说明该 key 正在刷新
     */
    private static final Map<String, AtomicInteger> REFRESH_LOCK = new ConcurrentHashMap<>();

    protected AbstractRedisOperator(LzmhRedisProperties lzmhRedisProperties, RedisService redisService) {
        this.lzmhRedisProperties = lzmhRedisProperties;
        this.redisService = redisService;
    }

    /**
     * 延长时间过期时间
     * @param expire 原来的过期时间
     * @return 延长后的过期时间
     */
    public long getExtendExpire(long expire) {
        return expire + lzmhRedisProperties.getExtendExpire();
    }


    /**
     * 从编码后的数据中返回解码后的数据
     * @param data 编码后的数据
     * @param codecEnum CodecEnum
     * @return 解码后的数据
     */
    public Object getDecodeData(Object data, CodecEnum codecEnum) {
        Codec codec = CodecFactory.getByType(codecEnum);
        return codec.decode(data);
    }

    /**
     * 获取编码数据
     * @param expire 过期时间
     * @param data 编码前的数据
     * @param codecEnum codecEnum
     * @return 编码后的数据
     */
    public Object getEncodeData(long expire, Object data, CodecEnum codecEnum) {
        Codec codec = CodecFactory.getByType(codecEnum);

        return codec.encode(expire, data);
    }

    /**
     *缓存无数据时，填充缓存逻辑
     * @param fillCache 填充缓存信息接口
     * @return 对象
     */
    protected <T> T fillCache(FillCache<T> fillCache) {
        boolean lock = false;
        String key = fillCache.getKey();
        AtomicInteger refreshLock = null;
        try{
            lock = this.tryLock(key);
            if (log.isDebugEnabled()) {
                log.debug("锁的情况:{}", lock);
            }
            if (!lock) {
                //没有获取到锁，走阻塞降级策略
                return fillCache.getIgnoreValidData();
            }

            refreshLock = this.getRefreshLock(key);
            if (refreshLock.compareAndSet(0, 1)) {
                //执行具体的获取缓存数据逻辑
                return fillCache.getCacheData();
            }
        }finally {
            if (lock) {
                //释放锁
                this.unlock(key);
                if (refreshLock != null) {
                    refreshLock.set(0);
                }
            }
        }
        //正常逻辑，不会走到这里
        return fillCache.getIgnoreValidData();
    }

    private void unlock(String key) {
        this.redisService.deleteObject(Constants.LOADING_KEY + key);
    }

    /**
     * 尝试获取锁，如果获取到锁，则可以执行缓存刷新操作
     * @param key 键值
     * @return 是否成功
     */
    private boolean tryLock(String key) {
        //判断当前 key 是否正在刷新
        AtomicInteger refreshLock = this.getRefreshLock(key);
        if (refreshLock.get() == 1) {
            return false;
        }

        //本地锁，避免频繁调用分布式锁
        Lock localLock = this.getLocalLock(key);
        boolean lock = localLock.tryLock();
        try {
            //获取到本地锁才执行远程分布式锁的获取
            if (lock) {
                //设置缓存最长刷新时间为 loadingKeyExpire ，在该时段内，只有一个线程可以获取到锁
                return this.redisService.setIfNotExist(Constants.LOADING_KEY + key, key, lzmhRedisProperties.getLoadingKeyExpire());
            }
        } finally {
            if (lock) {
                localLock.unlock();
            }
        }
        return false;
    }

    private Lock getLocalLock(String key) {
        LOCAL_LOCK.putIfAbsent(key, new ReentrantLock());
        return LOCAL_LOCK.get(key);
    }

    /**
     * 获取正在刷新的原子变量
     * @param key 键值
     * @return 原子变量
     */
    private AtomicInteger getRefreshLock(String key) {
        REFRESH_LOCK.putIfAbsent(key, new AtomicInteger());
        return REFRESH_LOCK.get(key);
    }

    /**
     * 如果缓存中有数据，则返回缓存中的数据；如果缓存中无数据，则循环遍历查询缓存中的数据；
     * 如果在指定时间内没有获取到数据，则执行Mock降级逻辑
     * @param key key
     * @return 获取缓存中的数据
     */
    public Object blockIfNeed(String key) {
        Object res;

        Lock localLock = this.getLocalLock(key);
        localLock.lock();
        try {
            //缓存中无数据，则循环遍历获取缓存中的数据
            long startTime = System.currentTimeMillis();
            while ((res = this.getDataIgnoreValid(key)) == null) {
                ThreadUtil.sleep(lzmhRedisProperties.getRetryInterval(), TimeUnit.MILLISECONDS);
                if (System.currentTimeMillis() - startTime > lzmhRedisProperties.getBlockTime()) {
                    break;
                }
            }
            if (res != null) {
                MapData mapData = (MapData) this.getDecodeData(res, CodecEnum.MAP);
                if (mapData.getData().size() == 0) {
                    return null;
                }
                return res;
            }

            //阻塞一定时间后还是获取不到数据，则执行降级逻辑
            MockRegister mockRegister = MockRegister.getInstance();
            Iterator<Mock> mockCacheOperators = mockRegister.getMockCacheOperators();
            while (mockCacheOperators.hasNext()) {
                Mock mock = mockCacheOperators.next();
                //执行mock逻辑
                Object result = mock.mock(key, null);
                if (result != null) {
                    return result;
                }
            }

        } finally {
            localLock.unlock();
        }

        return null;
    }

    /**
     * 获取数据，缓存中无数据，则返回null，那么将会循环获取缓存中
     * @param key key
     * @return 缓存中的数据
     */
    protected abstract Object getDataIgnoreValid(String key);
}
